package com.wll.thread.oom;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @ClassName: ThreadLocalOOMDemo.java
 * @author: wanglulu
 * @version: 1.0.0
 * @Description: TODO
 * @createTime: 2021/11/22 0022 21:17
 */
public class ThreadLocalOOMDemo {


    /**
     * 模拟了一个线程数为THREAD_LOOP_SIZE的线程池，所有线程共享一个 ThreadLocal 变量，
     * 每一个线程执行的时候插入一个大的 List 集合，这里由于执行了500 次循环，
     * 也就是产生了500个线程，每一个线程都会依附一个 ThreadLocal 变量：
     */

    // 线程数
    private static final int THREAD_LOOP_SIZE = 500;
    // 数组数量
    private static final int MOCK_BIG_DATA_LOOP_SIZE = 10000;

    private static ThreadLocal<List<User>> threadLocal = new ThreadLocal<>();


    /**
     * 下面我们分两种情况讨论：
     *
     * （1）key 使用强引用：引用的ThreadLocal的对象被回收了，但是ThreadLocalMap还持有ThreadLocal的强引用，如果没有手动删除，ThreadLocal不会被回收，导致 Entry 内存泄漏。
     *
     * （2）key 使用弱引用：引用的 ThreadLocal 的对象被回收了，由于ThreadLocalMap持有ThreadLocal的弱引用，即使没有手动删除，ThreadLocal也会被回收。value在下一次ThreadLocalMap调用set、get、remove的时候会被清除。
     *
     * 比较两种情况，我们可以发现：由于ThreadLocalMap的生命周期跟 Thread 一样长，如果都没有手动删除对应 key，都会导致内存泄漏，但是使用弱引用可以多一层保障：弱引用ThreadLocal不会内存泄漏，对应的 value 在下一次ThreadLocalMap调用set、get、remove的时候会被清除。
     *
     * 因此，ThreadLocal 内存泄漏的根源是：由于 ThreadLocalMap 的生命周期跟 Thread 一样长，如果没有手动删除对应 key 就会导致内存泄漏，而不是因为弱引用。
     *
     * ThreadLocal 最佳实践
     * 1、综合上面的分析，我们可以理解 ThreadLocal 内存泄漏的前因后果，那么怎么避免内存泄漏呢？
     *
     * 答案就是：每次使用完 ThreadLocal，都调用它的 remove() 方法，清除数据。
     *
     * 关于线程池：快速搞定线程池
     *
     * 在使用线程池的情况下，没有及时清理 ThreadLocal，不仅是内存泄漏的问题，更严重的是可能导致业务逻辑出现问题。所以，使用 ThreadLocal 就跟加锁完要解锁一样，用完就清理。
     *
     * 注意：
     *
     * 并不是所有使用 ThreadLocal 的地方，都在最后 remove()，他们的生命周期可能是需要和项目的生存周期一样长的，所以要进行恰当的选择，以免出现业务逻辑错误！但首先应该保证的是 ThreadLocal 中保存的数据大小不是很大！
     *
     * 2、那么我们修改最开始的代码为：
     *
     * 取消注释：threadLocal.remove(); 结果不会出现 OOM，可以看出堆内存的变化呈现锯齿状，证明每一次 remove() 之后，ThreadLocal 的内存释放掉了！线程池中的线程的数量持续增加！
     *
     * @param args
     * @throws InterruptedException
     */

    public static void main(String[] args) throws InterruptedException {
        System.out.println("start--------------->>>");
        ExecutorService executorService = Executors.newFixedThreadPool(THREAD_LOOP_SIZE);
        for (int i = 0; i < THREAD_LOOP_SIZE; i++) {
            executorService.execute(() -> {
                threadLocal.set(new ThreadLocalOOMDemo().addBigList());
                Thread t = Thread.currentThread();
                System.out.println(Thread.currentThread().getName());
                threadLocal.remove();
            });
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("end--------------->>>");

    }



    private List<User> addBigList() {
        List<User> params = new ArrayList<>(MOCK_BIG_DATA_LOOP_SIZE);
        for (int i = 0; i < MOCK_BIG_DATA_LOOP_SIZE; i++) {
            params.add(new User("xuliugen", "password" + i, "男", i));
        }
        return params;
    }

    class User {
        private String userName;
        private String password;
        private String sex;
        private int age;

        public User(String userName, String password, String sex, int age) {
            this.userName = userName;
            this.password = password;
            this.sex = sex;
            this.age = age;
        }
    }


}
